package com.edev.support.web;

import com.edev.support.ddd.DddFactory;
import com.edev.support.entity.Entity;
import com.edev.support.utils.BeanUtils;
import com.edev.support.utils.DowncastHelper;
import com.edev.support.utils.ParameterHelper;
import com.edev.support.utils.SpringHelper;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * ORM服务动态调用控制器
 * <p>
 * 代码示例：
 * <pre>
 * 1. 定义服务类UserService.java，并在Spring容器中注册为user，包含create方法：
 * curl -X POST http://localhost:8080/orm/user/create \
 * -H "Content-Type: application/json" \
 * -d '{"name": "John", "age": 30}'
 * 2. 定义服务类UserService.java，并在Spring容器中注册为user，包含load方法：
 * curl http://localhost:8080/orm/user/load?userId=1
 * </pre>
 * <p>
 * 核心功能：
 * 1. 通过HTTP请求动态调用Spring容器中的Bean方法
 * 2. 支持GET/POST请求参数自动转换
 * 3. 处理实体类与JSON数据的双向转换
 * <p>
 * 典型使用场景：
 * - 快速构建基于Spring Bean的REST API
 * - 动态调用领域服务方法
 * - 前端与领域层的解耦调用
 * @author fangang
 */
@RestController
public class OrmController extends AbstractController {
    @Autowired
    private SpringHelper springHelper;
    @Autowired
    private DddFactory dddFactory;
    @Autowired
    private DowncastHelper helper;

    /**
     * 通用方法执行入口（支持GET/POST）
     * @param beanName Spring容器中的Bean名称
     * @param methodName 需要调用的方法名称
     * @param json POST请求的JSON参数（可为空）
     * @param request GET请求的HttpServletRequest对象
     * @return 方法执行结果
     */
    @RequestMapping(value = "orm/{bean}/{method}", method = {RequestMethod.GET, RequestMethod.POST})
    public Object perform(@PathVariable("bean") String beanName,
                          @PathVariable("method") String methodName,
                          @RequestBody(required = false) Map<String, Object> json,
                          HttpServletRequest request) {
        Object service = springHelper.getService(beanName);
        Method method = BeanUtils.getMethod(service, methodName);
        Map<String, Object> params = mergeDataToJson(json, request);
        Object[] args = getArguments(method, params);
        return BeanUtils.invoke(service, method, args);
    }

    /**
     * 通用方法执行入口（只支持POST请求）
     * @param beanName Spring容器中的Bean名称
     * @param methodName 需要调用的方法名称
     * @param json POST请求的JSON参数（可为空）
     * @return 方法执行结果
     */
    public Object doPost(@NonNull String beanName, @NonNull String methodName, Map<String, Object> json) {
        return perform(beanName, methodName, json, null);
    }

    /**
     * 通用方法执行入口（只支持GET请求）
     * @param beanName Spring容器中的Bean名称
     * @param methodName 需要调用的方法名称
     * @param request GET请求的HttpServletRequest对象
     * @return 方法执行结果
     */
    public Object doGet(@NonNull String beanName, @NonNull String methodName, @NonNull HttpServletRequest request) {
        return perform(beanName, methodName, null, request);
    }

    /**
     * 处理列表类型参数的执行入口（只支持POST请求）
     * @param beanName Spring容器中的Bean名称
     * @param methodName 需要调用的方法名称
     * @param list 请求体中的列表数据
     * @return 方法执行结果
     */
    @PostMapping("list/{bean}/{method}")
    public Object list(@PathVariable("bean") String beanName,
                       @PathVariable("method") String methodName,
                       @RequestBody List<Object> list) {
        Object service = springHelper.getService(beanName);
        Method method = BeanUtils.getMethod(service, methodName);
        Object[] args = getArgumentsForList(method, list);
        return BeanUtils.invoke(service, method, args);
    }

    public Object doList(@NonNull String beanName, @NonNull String methodName, List<Object> list) {
        return list(beanName, methodName, list);
    }

    /**
     * 获取被调方法的所有参数：
     * 1. 实体类型参数：从JSON中解析成实体对象
     * 2. 列表类型参数：从JSON中解析成实体列表
     * 3. 其他类型参数：从请求参数中根据参数名获取，并将其转换成该目标类型
     * @param <E> 实体类型
     * @param <S> 主键类型
     * @param method 要调用的方法
     * @param json POST请求的JSON参数
     * @return 方法参数数组
     */
    private <E extends Entity<S>, S extends Serializable>
            Object[] getArguments(@NonNull Method method, Map<String, Object> json) {
        //There is no parameter to be used from json
        if(json==null||json.isEmpty()) return new Object[]{};
        Parameter[] parameters = ParameterHelper.build(method).getParameters();
        //The method has no parameter
        if(parameters.length == 0) return new Object[]{};

        List<Object> args = new ArrayList<>();
        for(Parameter parameter : parameters) {
            if(dddFactory.isEntity(parameter.getType())) {
                @SuppressWarnings("unchecked")
                Class<E> clazz = (Class<E>) parameter.getType();
                E entity = dddFactory.createEntityByJson(clazz, json);
                args.add(entity);
            } else if(dddFactory.isListOrSetOfEntities(parameter.getParameterizedType())) {
                Type type = parameter.getParameterizedType();
                String name = parameter.getName();
                Object value = json.get(name);
                List<E> entities = dddFactory.createEntityByJsonForList(type, value);
                args.add(entities);
            } else {
                String name = parameter.getName();
                Object value = json.get(name);
                value = helper.downcast(parameter.getParameterizedType(), value);
                args.add(value);
            }
        }
        return args.toArray();
    }

    /**
     * 获取被调方法所需的列表参数，对于列表参数中的每个成员：
     * 1. 实体类型参数：从JSON中解析成实体对象
     * 2. 列表类型参数：从JSON中解析成实体列表
     * 3. 其他类型参数：从请求参数中根据参数名获取，并将其转换成该目标类型
     * @param <E> 实体类型
     * @param <S> 主键类型
     * @param method 要调用的方法
     * @param list 请求体中的列表数据
     * @return 方法参数数组
     */
    private <E extends Entity<S>, S extends Serializable>
            Object[] getArgumentsForList(@NonNull Method method, List<Object> list) {
        if(list==null||list.isEmpty()) return new Object[]{};
        Parameter[] parameters = method.getParameters();
        if(parameters.length!=1)
            throw new WebException("The parameter must be a single list, or use 'orm/{bean}/{method}' request");
        Parameter parameter = parameters[0];
        List<Object> args = new ArrayList<>();
        if(dddFactory.isListOrSetOfEntities(parameter.getParameterizedType())) {
            Type type = parameter.getParameterizedType();
            List<E> entities = dddFactory.createEntityByJsonForList(type, list);
            args.add(entities);
        } else {
            Object value = helper.downcast(parameter.getParameterizedType(), list);
            args.add(value);
        }
        return args.toArray();
    }
}
